package com.google.code.chaotic.ejb;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;

import javax.ejb.EJB;
import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.ejb.Timer;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

import com.google.code.chaotic.EmFactory;
import com.google.code.chaotic.dto.EndType;
import com.google.code.chaotic.model.CommutingHabit;
import com.google.code.chaotic.model.CommutingService;
import com.google.code.chaotic.model.HabitInService;
import com.google.code.chaotic.model.ModelUtils;

/**
 * @author hadi
 */
@Stateless
public class AnalysisTimer implements AnalysisTimerLocal {

	private static final Logger logger = Logger //
			.getLogger(AnalysisTimer.class.getName());

	/**
	 * @uml.property name="messagingService"
	 * @uml.associationEnd
	 */
	@EJB
	private MessagingServiceBeanLocal messagingService;

	//

	@Schedule(second = "*/5", minute = "*", hour = "*", dayOfWeek = "*", dayOfMonth = "*", month = "*", year = "*", info = "EmFactory Poker")
	void initEmFactory() {
		EmFactory.get();
	}

	// @Schedule(second="*/10", minute="*", hour="8-23", dayOfWeek="Mon-Fri",
	// dayOfMonth="*", month="*", year="*", info="MyTimer")
	// @Schedule(second = "0", minute = "0", hour = "4,12", dayOfWeek = "*",
	// dayOfMonth = "*", month = "*", year = "*", info = "Analysis Timer")
	public synchronized void scheduledTimeout(final Timer t) //
			throws ClassNotFoundException, SQLException {

		logger.info("!!!!!!!!!! AnalysisTimer INVOKED...");

		EntityManagerFactory emf = EmFactory.get();
		EntityManager em = emf.createEntityManager();
		Statement stmt1 = null;
		Statement stmt2 = null;
		try {
			Connection conn1 = EjbUtils.getConnection(emf);
			stmt1 = conn1.createStatement();

			String operation;

			_logBefore(operation = "primitive-to-gis conversion");
			// TODO: Make sure the 'orig_gis' and 'dest_gis' are
			// already created and indexed before executing the following!
			stmt1.execute("SELECT chaos_habits_to_gis()");
			_logAfter(operation);

			String //
			sqlStmt1 = "SELECT * from public.java_list_commuting_groups(2) "
					+ "       AS (id varchar, mate_count int, mates_ids varchar) "
					+ "ORDER BY mate_count DESC";

			_logBefore(operation = "analysis SP");
			ResultSet rs1 = stmt1.executeQuery(sqlStmt1);
			_logAfter(operation);

			EntityTransaction tx = em.getTransaction();
			tx.begin();
			// TODO: The following two deletions must be removed when
			// the system incorporates time analysis too.
			em.createQuery("DELETE FROM HabitInService his").executeUpdate();
			em.createQuery("DELETE FROM ServiceWaypoint sw").executeUpdate();
			em.createQuery("DELETE FROM CommutingService s").executeUpdate();

			_logBefore(operation = "generating offers");
			while (rs1.next()) {
				String habitId = rs1.getString("id");
				CommutingHabit habit = em.find(CommutingHabit.class, habitId);
				String matesIds = rs1.getString("mates_ids");
				matesIds = matesIds.substring(1, matesIds.length() - 1);

				String[] idArray = matesIds.split(",");
				if (idArray.length < 2)
					continue;

				CommutingService service = new CommutingService();
				List<HabitInService> hisList = new LinkedList<HabitInService>();
				HabitInService his = new HabitInService();
				his.setHabit(habit);
				his.setCancellingCode(generateCancellingCode(habit));
				his.setService(service);
				hisList.add(his);

				List<CommutingHabit> habitList = new LinkedList<CommutingHabit>();
				habitList.add(habit);

				for (String mateId : idArray) {
					CommutingHabit mate = em.find(CommutingHabit.class, mateId
							.trim());
					HabitInService tempHIS = new HabitInService();
					tempHIS.setHabit(mate);
					tempHIS.setCancellingCode(generateCancellingCode(mate));
					tempHIS.setService(service);
					hisList.add(tempHIS);

					habitList.add(mate);
				}
				service.setHabits(hisList);
				service.setOrigin(ModelUtils.meanLatLon( //
						ModelUtils.extractHabitEnds(habitList, EndType.ORIG)));
				service.setDestination(ModelUtils.meanLatLon( //
						ModelUtils.extractHabitEnds(habitList, EndType.DEST)));

				em.persist(service);
			}
			_logAfter(operation);
			tx.commit();

			// TODO: Must be removed for roll-out
			if (messagingService == null) {
				messagingService = new MessagingServiceBean();
			}

			String //
			sqlStmt2 = "SELECT id FROM commuting_service " //
					+ " ORDER BY id";
			Connection conn2 = EjbUtils.getConnection(emf);
			stmt2 = conn2.createStatement();
			ResultSet rs2 = stmt2.executeQuery(sqlStmt2);

			int preRollOutLimit = 2; // TODO: Must be removed for the roll-out
			while (rs2.next() && preRollOutLimit-- > 0)
				messagingService.informCommutersInService(rs2.getString("id"));

			logger.info("!!!!!!!!!! AnalysisTimer DELIVERED.");

		} finally {
			if (stmt1 != null)
				stmt1.close();
			if (stmt2 != null)
				stmt2.close();
			em.close();
			// pm.close();
			logger.info("!!!!!!!!!! AnalysisTimer IN finally.");
		}
	}

	private String generateCancellingCode(CommutingHabit habit) {
		StringBuilder code = new StringBuilder();
		UUID uuid = UUID.randomUUID();
		code.append(Long.toHexString(uuid.getLeastSignificantBits()));
		code.append(Integer.toHexString(habit.hashCode()));
		code.append(Long.toHexString(uuid.getMostSignificantBits()));
		return code.toString();
	}

	private static long lastMillis;

	private static void _logBefore(String operation) {
		logger.info(String.format(">>>>>>>>>> BEFORE: %1$s, TIMESTAMP: %2$d",
				operation, lastMillis = System.currentTimeMillis()));
	}

	private static void _logAfter(String operation) {
		logger.info(String.format(">>>>>>>>>>  AFTER: %1$s,   ELAPSED: %2$d",
				operation, System.currentTimeMillis() - lastMillis));
	}

	public static void main(String[] args) //
			throws ClassNotFoundException, SQLException {

		new AnalysisTimer().scheduledTimeout(null);
	}

	public void fireScheduledTimeout() throws Exception {
		scheduledTimeout(null);
	}
}
